/**
 * @Author: Gosin
 * @Date: 2020/3/23 15:44
 */
package lib

import (
	"crypto"
	"crypto/md5"
	"crypto/rsa"
	"crypto/x509"
	"encoding/hex"
	"encoding/xml"
	"errors"
	"fmt"
	"git.900sui.cn/kc/base/common/functions"
	"git.900sui.cn/kc/kcgin"
	"github.com/shopspring/decimal"
	"github.com/tidwall/gjson"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	curcode     = "01"
	urls        = "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6"
	ccbToolsUrl = "http://47.110.132.24:15613/ccbtools/"
)

type ccbpay struct {
	Merchantid string
	Posid      string
	Branchid   string
	PublicKey  string
	Curcode    string

	Url string
}

type PayList struct {
	Id     string
	Amount string
}

type orderInfo struct {
	Amount    string `xml:"TX_INFO>LIST>Orig_Amt"`              // 总金额
	PayTime   string `xml:"TX_INFO>LIST>TRAN_DATE"`             // 交易时间
	RealPrice string `xml:"TX_INFO>LIST>Txn_ClrgAmt"`           // 实际到账金额
	Fee       string `xml:"TX_INFO>LIST>MrchCmsn_Amt"`          // 手续费
	PaySn     string `xml:"TX_INFO>LIST>OriOvrlsttnEV_Trck_No"` // 订单流水号
	Status    string `xml:"TX_INFO>LIST>ORDER_STATUS"`          // 订单状态
}

var (
	noSign = []string{"FZINFO2", "THIRDAPPINFO", "SUB_APPID"}
	CcbPay *ccbpay
)

func init() {
	CcbPay = &ccbpay{
		Merchantid: kcgin.AppConfig.String("ccbpay.associationMerchantId"),
		Branchid:   kcgin.AppConfig.String("ccbpay.branchId"),
		Posid:      kcgin.AppConfig.String("ccbpay.posId"),
		PublicKey:  kcgin.AppConfig.String("ccbpay.ccbpayPublicKey"),
		Curcode:    curcode,
		Url:        urls,
	}
}

// 分账初始化
func (c *ccbpay) CheckIn(orderId string, payLists *[]PayList) bool {
	data := map[string]string{
		"MERCHANTID": c.Merchantid,
		"POSID":      "038595766", //c.Posid,
		"BRANCHID":   c.Branchid,
		"ORDERID":    orderId,
		"PAYMENT":    "0",
		"CURCODE":    c.Curcode,
		"TXCODE":     "PAY4FZ",
		"FZINFO1":    "32!",
		"FZINFO2":    "",
		"PUB":        "",
	}
	mapsort := []string{
		"MERCHANTID",
		"POSID",
		"BRANCHID",
		"ORDERID",
		"PAYMENT",
		"CURCODE",
		"TXCODE",
		"FZINFO1",
		"FZINFO2",
		"PUB",
	}
	var payment decimal.Decimal
	for _, payList := range *payLists {
		amount, _ := decimal.NewFromString(payList.Amount)
		if amount.Cmp(decimal.Zero) != 1 {
			continue
		}
		payment = payment.Add(amount)

		fzinf := "01^" + payList.Id + "^^^" + payList.Amount + "^0#"
		if len(data["FZINFO1"])+len(fzinf) <= 203 {
			data["FZINFO1"] += fzinf
		} else {
			data["FZINFO2"] += fzinf
		}
	}
	data["FZINFO1"] = url.QueryEscape(data["FZINFO1"])
	data["FZINFO2"] = url.QueryEscape(data["FZINFO2"])
	data["PAYMENT"] = payment.String()

	c.getSign(data, mapsort)

	res, err := c.Exec(data)
	if err != nil {
		return false
	}
	if res.Get("SUCCESS").String() == "true" && res.Get("ERRCODE").String() == "000000" {
		return true
	}
	return false
}

// 获取二维码支付链接
func (c *ccbpay) Qrurl(orderId string, payment string, timeout int, wxappid string) string {
	data := map[string]string{
		"MERCHANTID": c.Merchantid,
		"POSID":      c.Posid,
		"BRANCHID":   c.Branchid,
		"ORDERID":    orderId,
		"PAYMENT":    payment,
		"CURCODE":    c.Curcode,
		"TXCODE":     "530550",
		"REMARK1":    "",
		"REMARK2":    "",
		"RETURNTYPE": "3",
		"TIMEOUT":    time.Now().Add(time.Second * time.Duration(timeout)).Format("20060102150405"),
		"PUB":        "",
	}
	mapsort := []string{
		"MERCHANTID",
		"POSID",
		"BRANCHID",
		"ORDERID",
		"PAYMENT",
		"CURCODE",
		"TXCODE",
		"REMARK1",
		"REMARK2",
		"RETURNTYPE",
		"TIMEOUT",
		"PUB",
		"SUB_APPID",
	}
	if wxappid != "" {
		data["SUB_APPID"] = wxappid
	}

	c.getSign(data, mapsort)

	res, err := c.Exec(data)
	if err != nil {
		return ""
	}
	if res.Get("QRURL").String() != "" {
		re, _ := url.QueryUnescape(res.Get("QRURL").String())
		return re
	}
	return ""
}

// 获取小程序支付信息
func (c *ccbpay) Wxapp(orderId string, payment string, timeout int, wxappid string, openid string) *gjson.Result {
	data := map[string]string{
		"MERCHANTID": c.Merchantid,
		"POSID":      c.Posid,
		"BRANCHID":   c.Branchid,
		"ORDERID":    orderId,
		"PAYMENT":    payment,
		"CURCODE":    c.Curcode,
		"TXCODE":     "530590",
		"REMARK1":    "",
		"REMARK2":    "",
		"TYPE":       "1",
		"PUB":        "",
		"GATEWAY":    "0",
		"CLIENTIP":   "",
		"REGINFO":    "",
		"PROINFO":    "",
		"REFERER":    "",
		"TIMEOUT":    time.Now().Add(time.Second * time.Duration(timeout)).Format("20060102150405"),
		"TRADE_TYPE": "MINIPRO",
		"SUB_APPID":  wxappid,
		"SUB_OPENID": openid,
	}
	mapsort := []string{
		"MERCHANTID",
		"POSID",
		"BRANCHID",
		"ORDERID",
		"PAYMENT",
		"CURCODE",
		"TXCODE",
		"REMARK1",
		"REMARK2",
		"TYPE",
		"PUB",
		"GATEWAY",
		"CLIENTIP",
		"REGINFO",
		"PROINFO",
		"REFERER",
		"TIMEOUT",
		"TRADE_TYPE",
		"SUB_APPID",
		"SUB_OPENID",
	}
	c.getSign(data, mapsort)

	res, err := c.Exec(data)
	if err != nil {
		return nil
	}
	if res.Get("SUCCESS").String() != "true" {
		return nil
	}
	return res
}

// 获取app支付信息
func (c *ccbpay) App(orderId string, payment string, timeout int) *gjson.Result {
	data := map[string]string{
		"MERCHANTID": c.Merchantid,
		"POSID":      c.Posid,
		"BRANCHID":   c.Branchid,
		"ORDERID":    orderId,
		"PAYMENT":    payment,
		"CURCODE":    c.Curcode,
		"TXCODE":     "520100",
		"REMARK1":    "",
		"REMARK2":    "",
		"TYPE":       "1",
		"PUB":        "",
		"GATEWAY":    "",
		"CLIENTIP":   "",
		"REGINFO":    "",
		"PROINFO":    "",
		"REFERER":    "",
		"TIMEOUT":    time.Now().Add(time.Second * time.Duration(timeout)).Format("20060102150405"),
	}
	mapsort := []string{
		"MERCHANTID",
		"POSID",
		"BRANCHID",
		"ORDERID",
		"PAYMENT",
		"CURCODE",
		"TXCODE",
		"REMARK1",
		"REMARK2",
		"TYPE",
		"PUB",
		"GATEWAY",
		"CLIENTIP",
		"REGINFO",
		"PROINFO",
		"REFERER",
		"TIMEOUT",
	}
	c.getSign(data, mapsort)

	postData := url.Values{}
	for key, value := range data {
		postData.Set(key, value)
	}
	res := gjson.Result{
		Type: gjson.String,
		Str:  postData.Encode(),
	}
	return &res
}

func (c *ccbpay) GetOrderInfo(orderSn string) (*orderInfo, error) {
	result, err := request("post", ccbToolsUrl+"5W1002", fmt.Sprintf("<TX_INFO><ORDER>%s</ORDER></TX_INFO>", orderSn))
	if err != nil {
		return nil, err
	}
	res := gjson.Parse(result)
	if res.Get("error").String() == "0" {
		xmlData := res.Get("data").String()
		xmlData = strings.TrimLeft(xmlData, `<?xml version="1.0" encoding="GB18030"?>`)
		info := &orderInfo{}
		if err := xml.Unmarshal([]byte(xmlData), &info); err != nil {
			return nil, err
		}
		return info, nil
	}
	return nil, errors.New(res.Get("errorMsg").String())
}

// 签名
func (c *ccbpay) getSign(data map[string]string, mapsort []string) {
	data["PUB"] = c.PublicKey[len(c.PublicKey)-30:]
	signTxt := ""
	functions.InArray("", "")
	for _, key := range mapsort {
		if value, ok := data[key]; ok {
			if value == "" && functions.InArray(key, noSign) {
				continue
			}
			if signTxt != "" {
				signTxt += "&" + key + "=" + value
			} else {
				signTxt += key + "=" + value
			}
		}
	}
	data["MAC"] = functions.HashMd5(signTxt)
	delete(data, "PUB")
}

// 验签
func (c *ccbpay) Valid(data map[string]string) bool {
	if signdata, ok := data["SIGN"]; ok && signdata != "" {
		mapsort := []string{
			"POSID",
			"BRANCHID",
			"ORDERID",
			"PAYMENT",
			"CURCODE",
			"REMARK1",
			"REMARK2",
			"ACC_TYPE",
			"SUCCESS",
			"TYPE",
			"REFERER",
			"CLIENTIP",
			"ACCDATE",
		}
		signTxt := ""
		for _, key := range mapsort {
			if value, ok := data[key]; ok {
				if value == "" && functions.InArray(key, noSign) {
					continue
				}
				if signTxt != "" {
					signTxt += "&" + key + "=" + value
				} else {
					signTxt += key + "=" + value
				}
			}
		}
		sign, err := hex.DecodeString(signdata)
		if err != nil {
			return false
		}
		publicKey, err := hex.DecodeString(c.PublicKey)
		if err != nil {
			return false
		}
		pub, err := x509.ParsePKIXPublicKey(publicKey)
		if err != nil {
			return false
		}
		hash := md5.New()
		hash.Write([]byte(signTxt))
		err = rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.MD5, hash.Sum(nil), sign)
		return err == nil
	}
	return false
}

func (c *ccbpay) Exec(data map[string]string) (*gjson.Result, error) {
	postData := url.Values{}
	for key, value := range data {
		postData.Set(key, value)
	}
	res, err := request("post", c.Url, postData.Encode(), &map[string]string{"Content-type": "application/x-www-form-urlencoded"})
	if err != nil {
		return nil, err
	}
	result := gjson.Parse(res)
	if result.Get("PAYURL").String() != "" {
		res, err := request("get", result.Get("PAYURL").String(), "")
		if err != nil {
			return nil, err
		}
		result := gjson.Parse(res)
		return &result, nil
	}
	return nil, errors.New(result.Get("").String())
}

// 网络请求
func request(method string, host string, data string, headers ...*map[string]string) (string, error) {
	client := &http.Client{}
	method = strings.ToUpper(method)

	req, err := http.NewRequest(method, host, strings.NewReader(data))
	if err != nil {
		return "", err
	}
	if len(headers) > 0 {
		for key, value := range *headers[0] {
			req.Header.Add(key, value)
		}
	}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}
